Vue
official::Vue.js
text::Javascript
挑战 | Vue.js挑战 (cn-vuejs-challenges.netlify.app)
JS语法框架对比:JavaScript框架语法特性对比 (lainbo.com)
一、Vue前置知识 1 命令
在使用命令前先要安装Node.js
text::Node.js安装
npm init vue@latest Project name >> Add TypeScript? Add JSX Support? Add Vue Router for Single Page Application development? Add Pinia for state management? Add Vitest for Unit testing? Add an End-to-End Testing Solution? Add ESLint for code quality? Add Prettier for code formatting? npm install cnpm install npm run dev npm run build
2 目录结构
.vscode:vscode工具的配置文件
node_modules:Vue项目的运行依赖文件(npm install安装后生成的文件夹)
public:资源文件夹(浏览器图标)
src:源码文件夹
.gitignore:git忽略文件
index.html:入口HTML文件。所有文件都在这个html中运行。
package.json:信息描述文件
README.md:注释文件
vite.config.js: Vue配置文件
注:
一般情况下public放动态资源,src/assets中放静态资源。public可以是放服务器动态请求的资源,而assets是放前端必须要的资源。因为assets中的东西在build以后就编译到程序中,无法进行修改。
<!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" /> <link rel ="icon" href ="/favicon.ico" /> <meta name ="viewport" content ="width=device-width, initial-scale=1.0" /> <title > Vite App</title > </head > <body > <div id ="app" > </div > <script type ="module" src ="/src/main.js" > </script > </body > </html >
{ "name" : "vue3_cli_default" , "version" : "0.0.0" , "scripts" : { "dev" : "vite" , "build" : "vite build" , "serve" : "vite preview" } , "dependencies" : { "vue" : "^3.2.8" } , "devDependencies" : { "@vitejs/plugin-vue" : "^1.6.0" , "@vue/compiler-sfc" : "^3.2.6" , "vite" : "^2.5.2" } }
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig ({ plugins : [vue ()] })
3 API风格
选项式API:Vue2写法。在组合式API的基础上实现。常用于项目中部分代码用Vue写。特点是在<script>
中分别完善对应的功能后再选择,例如:data()
、methods
等。
组合式API:Vue3写法。常用于整个项目都用Vue写。特点是可以将功能整合起来。
<script> // 选项式API export default { // data() 返回的属性将会成为响应式的状态 // 并且暴露在 this 上 data() { return { count: 0 } }, // methods 是一些用来更改状态与触发更新的函数 // 它们可以在模板中作为事件处理器绑定 methods: { increment() { this.count++ } }, // 生命周期钩子会在组件生命周期的各个不同阶段被调用 // 例如这个函数就会在组件挂载完成后被调用 mounted() { console.log(`选项式API:The initial count is ${this.count}.`) } } </script> <template> <button @click="increment">选项式API:Count is: {{ count }}</button> </template>
<script setup> // 组合式API import { ref, onMounted } from 'vue' // 响应式状态 const count = ref(0) // 用来修改状态、触发更新的函数 function increment() { count.value++ } // 生命周期钩子 onMounted(() => { console.log(`组合式API:The initial count is ${count.value}.`) }) </script> <template> <button @click="increment">组合式API:Count is: {{ count }}</button> </template>
二、Vue基础
下列写法都是选项式写法。
1 文本插值
script中的值会在template中直接显示出来。
每个绑定仅支持单一表达式,也就是一段能够被求值的JavaScript代码。一个简单的判断方法是是否可以合法地写在return后面。
注:在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成XSS 漏洞。请仅在内容安全可信时再使用 v-html
,并且永远不要 使用用户提供的 HTML 内容。
<template> <h3>模板语法</h3> <p>{{ msg }}</p> <p>{{ hello }}</p> <p>{{ number + 1 }}</p> <p>{{ ok ? 'YES' : 'NO' }}</p> <p>{{ message.split('').reverse().join('') }}</p> </template> <script> export default{ data(){ return { msg:"神奇的语法", hello:"Hello World!", number:10, ok:true, message:"大家好" } } } </script> <!-- 下列是无效语句 --> <!-- 这是一个语句,而非表达式 --> {{ var a = 1 }} <!-- 条件控制也不支持,请使用三元表达式 --> {{ if (ok) { return message } }}
<!-- 链接解析 --> <template> <p>{{ rawHtml }}</p> <!-- 原样输出链接 --> <p v-html="rawHtml"></p> <!-- 解析后嵌套在p标签内 --> </template> <script> export default{ data(){ return { rawHtml:"<a href='https://itbaizhan.com'>百战程序员</a>", } } } </script>
2 属性绑定
双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个attribute,应该使用v-bind
指令。
v-bind
指令指示Vue将元素的id
attribute 与组件的dynamicld
属性保持一致。如果绑定的值是null
或者undefined
,那么该attribute将会从渲染的元素上移除。
因为v-bind
非常常用,所以有简写语法,直接写个冒号即可。
<template> <div class="{{ msg }}">测试</div> <!-- class的值原样输出 --> <div v-bind:id="dynamicId" v-bind:class="msg">测试</div> <!-- 属性值转化后输出 --> <div v-bind:id="isNull" v-bind:class="isUndefined">测试</div> <!-- 属性值都被移除 --> <div :id="dynamicId" :class="msg">测试</div> <!-- 简写语法 --> <button :disabled="isButtonDisabled">不可点击</button> <!-- 可以动态改变按钮 --> <div v-bind="objectOfAttrs">测试</div> <!-- 一次绑定多个值 --> </template> <script> export default{ data(){ return { msg:"active", dynamicId:"appid", isNull:null, isUndefined:undefined, isButtonDisabled:true, objectOfAttrs:{ id: "appId", // 属性名:"属性值",可以自定义 class: "appClass", myAttr: "attr" } } } } </script> <!-- css会对类值为active的类生效 --> <style> .active{ color: red; font-size: 30px; } </style>
3 条件渲染
v-if,v-else-if,v-else
:为真则渲染,否则不渲染。
v-show
类似v-if
,也是条件判断,但是v-show
后面不可接else。
v-if
和v-show
:
v-if
是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
v-if
也是惰性的:如果在初次渲染时条件值为false,则不会做任何事。条件区块只有当条件首次变为true时才被渲染。
相比之下,v-show
简单许多,元素无论初始条件如何,始终会被渲染,只有CSS display
属性会被切换。
总的来说,v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用v-show
较好;如果在运行时绑定条件很少改变,则v-if
会更合适。
<template> <div v-if="flag">can you see me?</div> <!-- 修改flag的值看不同结果 --> <div v-else>You should see me.</div> <div v-if="type === 'A'">A</div> <!-- 修改type的值看不同结果 --> <div v-else-if="type === 'B'">B</div> <div v-else-if="type === 'C'">C</div> <div v-else>Not A/B/C</div> <div v-show="flag">show!</div> <!-- 修改flag的值看不同结果 --> <div v-else>No show!</div> </template> <script> export default{ data(){ return { flag:true, type:"D" } } } </script>
4 列表渲染
我们可以使用v-for
指令基于一个数组来渲染一个列表。v-for
指令的值需要使用item in items
形式的特殊语法,其中items
是源数据的数组,而item
是迭代项的别名。
列表有如下:
<p v-for="item in names">
:依次获取names中的值存到item中。
<p v-for="item of names">
:同理,用of也行。
<p v-for="(item,index) in names">
:依次获取names中的值存到item中,获得下标存入index中。
<p v-for="(item,index) of names">
:同理。
对象有如下:
<p v-for="(value,key,index) in userInfo">
:依次获得值,键,下标。
<template> <div v-for="item in names">{{ item }}</div> <!-- 获得列表中对象值 --> <div v-for="item in result"> <p>{{ item.title }}</p> <img :src="item.avator" alt="" /> </div> <!-- 获得对象值 --> <p v-for="(value,key,index) in userInfo">{{ value }} - {{ key }} - {{ index }}</p> </template> <script> export default { data() { return { names: ["uyi", "mio", "tsumugi"], result: [ { id: 1, title: '百度', avator: 'https://www.baidu.com/img/flexible/logo/pc/peak-result.png' }, { id: 2, title: '淘宝', avator: 'https://gw.alicdn.com/mt/TB1ZnvPLFXXXXa3XFXXXXXXXXXX-63-63.png' } ], userInfo: { name: "ikun", age: 24, sex: "man" } } } } </script>
5 Key管理
Vue默认按照”就地更新”的策略来更新通过v-for
渲染的元素列表。当数据项的顺序改变时,Vue不会随之移动DOM元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。
比如原始数据顺序是:1、2、3。当改成:1、3、2后,Vue不会仅仅改变两者的顺序,而是连带所有数据全部重新渲染,这样十分消耗性能。
为了给Vue一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的key attribute
。
温馨提示:
key
在这里是一个通过v-bind
绑定的特殊attribute
推荐在任何可行的时候为v-for
提供一个key attribute
key
绑定的值期望是一个基础类型的值,例如字符串或number类型
不建议使用index
作为key
的值,因为index
是会随数组改变而改变,我们要确保每一条数据的唯一索引不会改变,可以使用数据库中返回的id
作为索引。
<template> <!-- 用id作为唯一key --> <div v-for="(item,index) in result" :key="item.id"> <p>{{ item.title }}</p> <img :src="item.avator" alt="" /> </div> </template> <script> export default { data() { return { result: [ { id: 1, title: '百度', avator: 'https://www.baidu.com/img/flexible/logo/pc/peak-result.png' }, { id: 2, title: '淘宝', avator: 'https://gw.alicdn.com/mt/TB1ZnvPLFXXXXa3XFXXXXXXXXXX-63-63.png' } ] } } } </script>
6 事件管理
我们可以使用v-on
指令(简写为@
)来监听DOM事件,并在事件触发时执行对应的JavaScript。用法:v-on:click="methodName"
或@click="handler"
事件处理器的值可以是:
内联事件处理器:事件被触发时执行的内联JavaScript语句(与onclick
类似)。通常用于简单场景。
方法事件处理器:一个指向组件上定义的方法的属性名或是路径。
<!-- 内联事件处理器 --> <template> <h3>内联事件处理器</h3> <button @click="count++">Inline add 1</button> <p>Count is: {{ count }}</p> </template> <script> export default { data() { return { count: 0 } } } </script> <!-- 方法事件处理器 --> <template> <h3>方法事件处理器</h3> <button @click="addCount">Method add 1</button> <p>Count is: {{ count }}</p> </template> <script> export default { data() { return { count: 0 } }, // 所有的方法或者函数都放在这里 methods:{ addCount(){ this.count++; } } } </script>
7 事件传参
事件参数可以获取event
对象和通过事件传递数据。
<template> <!-- 无参传递事件 --> <button @click="addCount">Add</button> <p>{{ count }}</p> <!-- 有参传递事件 --> <p @click="getNameHandler($event,item)" v-for="(item,index) of names" :key="index">{{ item }}</p> </template> <script> export default { data() { return { count: 0, names: ["uyi","mio","tsumugi"] } }, methods:{ // 无参可以直接获得e addCount(e){ // 控制台输出 console.log(e.target.innerHTML = "A"); this.count++; }, // 带参获得事件(点击名字可以输出对应结果) getNameHandler(e,name) { console.log(name); console.log(e); } } } </script>
8 事件修饰符
在处理事件时调用event.preventDefault()
或event.stopPropagation()
是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理DOM事件的细节会更好。
official::更多修饰符
因此Vue为v-on
提供了事件修饰符,常见如下:
.stop
:阻止事件冒泡
.prevent
:阻止默认事件
.once
:事件只会被触发一次
.enter
:回车事件触发的
<template> <h3>点击但不跳转</h3> <a @click.prevent="clickHandle" href="https://www.baidu.com">百度</a> <h3>父元素不冒泡</h3> <!-- 阻止子元素向上冒泡到父元素 --> <div @click="clickDiv"> <p @click.stop="clickP">测试冒泡</p> </div> </template> <script> export default { data() { return { } }, methods:{ clickHandle(e){ console.log("被点了喵"); }, clickDiv(){ console.log("div被点了喵"); }, clickP(){ console.log("P被点了喵"); } } } </script>
9 数组侦测
变更方法:
Vue能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
替换一个数组:
变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一些不可变(immutable)方法,例如flter()
,concat()
和slice()
,这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的
<template> <!-- 数组变化帧听 --> <button @click="addListHandle">添加数据</button> <ul> <li v-for="(item,index) of names" :key="index">{{ item }}</li> </ul> <!-- 合并数组 --> <button @click="concatHandle">合并数组</button> <h3>数组1</h3> <p v-for="(item,index) of nums1" :key="index">{{ item }}</p> <h3>数组2</h3> <p v-for="(item,index) of nums2" :key="index">{{ item }}</p> </template> <script> export default { data() { return { names: ["uyi","mio","tsumugi"], nums1: [1,2,3,4,5], nums2: [6,7,8,9,10] } }, methods:{ addListHandle(){ // UI会自动更新 // this.names.push("azi"); // console.log(this.names); // 不会引起UI自动更新 this.names.concat(["azi"]); console.log(this.names.concat(["azi"])); // 重新赋值更新UI // this.names = this.names.concat(["azi"]); // console.log(this.names); }, concatHandle(){ // 同理上面 this.nums1 = this.nums1.concat(this.nums2); } } } </script>
10 计算属性
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。
重点区别:
<template> <h3>{{ itbaizhan.name }}</h3> <!-- 计算属性仅计算一次,不加括号 --> <p>{{ itbaizhanContent }}</p> <p>{{ itbaizhanContent }}</p> <p>{{ itbaizhanContent }}</p> <p>{{ itbaizhanContent }}</p> <!-- 函数会被调用四次 --> <p>{{ itbaizhanContents() }}</p> <p>{{ itbaizhanContents() }}</p> <p>{{ itbaizhanContents() }}</p> <p>{{ itbaizhanContents() }}</p> </template> <script> export default { data() { return { itbaizhan:{ name: "百战程序员", content: ["前端","Java","Python"] } } }, // 计算属性 computed:{ itbaizhanContent(){ return this.itbaizhan.content.length > 0 ? "Yes": "No"; } }, // 方法 methods:{ itbaizhanContents(){ return this.itbaizhan.content.length > 0 ? "Yes": "No"; } } } </script>
11 Class绑定
数据绑定的一个常见需求场景是操纵元素的CSS class
列表,因为class
是attribute
,我们可以和其他attribute
一样使用v-bind
将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue专门为class
的v-bind
用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。
在数组中也可以有条件地渲染某个class,使用三元表达式即可。
数组中也可以嵌套对象,但是对象中不能嵌套数组。
<template> <p :class="{'active': isActive,'text-danger': hasError}">Class样式绑定1</p> <!-- 可以直接绑定一个类 --> <p :class="classObject">Class样式绑定2</p> <!-- 可以绑定一个数组 --> <p :class="[arrActive,arrHasError]">Class样式绑定3</p> <!-- 有条件绑定 --> <p :class="[isActive ? 'active text-danger' : '']">Class样式绑定4</p> <!-- 数组嵌套对象 --> <p :class="[{ 'active':isActive }, errorClass]">Class样式绑定5</p> </template> <script> export default { data(){ return{ // 改变bool值使类带上对应值 isActive:true, hasError:false, classObject:{ 'active':true, 'text-danger':true }, arrActive:"active", arrHasError:"text-danger", errorClass:"text-danger" } } } </script> <style> .active{ font-size: 30px; } .text-danger{ color: red; } </style>
12 Style绑定
数据绑定的一个常见需求场景是操纵元素的CSS style
列表,因为style
是attribute
,我们可以和其他attribute
一样使用v-bind
将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue专门为style
的v-bind
用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。
一般不使用,因为Style绑定的权重很高,后期想改比较麻烦,要使用推荐使用class绑定。
<template> <!-- 记得加单位 --> <p :style="{color:activeColor,fontSize:fontSize +'px' }">Style绑定1</p> <!-- 绑定类 --> <p :style="styleobject" >Style绑定2</p> <!-- 绑定数组(一般不用) --> <p :style="[styleobject]">Style绑定3</p> </template> <script> export default { data(){ return{ activeColor: "green", fontSize: 30, styleobject: { color : "red", fontsize: "30px" } } } } </script>
13 侦听器
侦听数据的变化,然后执行对应操作。具有响应式的数据可以被侦听,比如{{ value }}
。
我们可以使用watch
选项在每次响应式属性发生变化时触发一个函数。
<template> <h3>侦听器</h3> <p>{{ message }}</p> <button @click="updateHandle">修改数据</button> </template> <script> export default { data(){ return{ message: "Hello" } }, methods: { updateHandle(){ this.message = "World"; } }, watch:{ // 与属性名相同(参数1是新数据,参数2是老数据) message(newValue,oldValue){ console.log(newValue,oldValue); } } } </script>
14 表单输入绑定
在前端处理表单时,我们常常需要将表单输入框的内容同步给JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦,v-model
指令帮我们简化了这一步骤。
v-model
也有修饰符
.lazy
默认情况下,v-model
会在每次input
事件后更新数据。你可以添加.lazy
修饰符来改为在每次change
事件后更新数据。比如输入完成后点击搜索或其他地方才会更改数据。
.number
:只接受数字类型。
.trim
:输出会去除前后空格。
<template> <h3>表单输入绑定</h3> <form> <!-- 数据回显 --> 数据:<input type="text" v-model="message"> <p>{{ message }}</p> <!-- 懒惰回显--> 懒惰:<input type="text" v-model.lazy="lazy"> <p>{{ lazy }}</p> <!-- 数字回显 --> 数字:<input type="text" v-model.number="number"> <p>{{ number }}</p> <!-- 去空格回显--> 左右去空格:<input type="text" v-model.trim="trim"> <p>{{ trim }}</p> <!-- 勾选修改 --> <input type="checkbox" id="checkbox" v-model="checked" /> <label for="checkbox">{{ checked }}</label> </form> </template> <script> export default { data(){ return{ message: "", lazy: "", number: "", trim: "", checked:false } } } </script>
15 模板引用
虽然Vue的声明性渲染模型为你抽象了大部分对DOM的直接操作,但在某些情况下,我们仍然需要直接访问底层DOM元素。要实现这一点,我们可以使用特殊的ref attribute
。
挂载结束后引用都会被暴露在this.$refs
之上。
对模板操作:
内容改变:{{ 模板语法 }}
属性改变:v-bind:指令
,:指令
事件:v-on:click
,@click
DOM:ref="name"
,$refs.ref_name.js_func
。如果没有特别的需求,不要操作DOM!
<template> <!-- 绑定ref用于执行JS函数 --> <div ref="container" class="container">{{ content }}</div> <input ref="username" type="text" /> <button @click="getElementHandle">获取元素</button> </template> <script> export default { data(){ return{ content:"内容" } }, methods:{ getElementHandle(){ // 通过$refs.ref_name.js_func执行原生JS函数 this.$refs.container.innerHTML = "哈哈哈"; console.log(this.$refs.username.value); } } } </script>
三、 Vue组件 1 组件基础
组件最大的优势就是可复用性。
当使用构建步骤时,我们一般会将Vue组件定义在一个单独的.vue
文件中,这被叫做单文件组件(简称SFC)
在components中创建的Vue,通过App.vue进行关联。
<!-- 组件书写 --> <!-- 承载Html标签(必有) --> <temp1ate> <div>承载标签</div> </template> <!-- 承载JS逻辑(可以没有) --> <script> export default {} </script> <!-- 承载CSS样式(可以没有) --> <!-- scoped表示样式只作用于当前内部元素,不会影响别的组件 --> <style scoped> </style>
<!-- 在App.vue中 --> <script setup> // 第一步:引入组件 import MyComponent from "./components/MyComponent.vue" export default { // 第二步:注入组件 components:{ MyComponent } } </script> <template> <!-- 第三步:显示组件,两种写法 --> <MyComponent/> <my-component/ > </template> <style> </style>
2 简单样式
APP.vue中引入:Header、Main、Aside。
Main.vue中继续引入:Article。
Aside.vue中继续引入:Item。
<!-- Header.vue--> <template> <h3>Header</h3> </template> <style scoped> h3{ width: 100%; height: 100px; border: 5px solid #999; text-align: center; line-height: 100px; box-sizing: border-box; } </style>
<!-- Article.vue --> <template> <h3>Article</h3> </template> <style scoped> h3{ width: 80%; margin: 0 auto; text-align: center; line-height: 100px; box-sizing: border-box; margin-top: 50px; background: #999; } </style>
<!-- Main.vue --> <template> <div class="main"> <h3>Main</h3> <Article/> <Article/> </div> </template> <script> import Article from "./Article.vue" export default { components: { Article } } </script> <style scoped> .main{ float: left; width: 70%; height: 400px; border: 5px solid #999; box-sizing: border-box; } </style>
<!-- Item.vue --> <template> <h3>Item</h3> </template> <style scoped> h3{ width: 80%; margin: 0 auto; text-align: center; line-height: 100px; box-sizing: border-box; margin-top: 10px; background: #999; } </style>
<!-- Aside.vue --> <template> <div class="aside"> <h3>Aside</h3> <Item /> <Item /> <Item /> </div> </template> <script> import Item from "./Item.vue" export default { components: { Item } } </script> <style scoped> .aside{ float: right; width: 30%; height: 400px; border: 5px solid #999; box-sizing: border-box; } </style>
<!-- App.vue --> <script> // This starter template is using Vue 3 <script setup> SFCs // Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup // 第一步:引入组件 import Header from "./components/Header.vue" import Main from "./components/Main.vue" import Aside from "./components/Aside.vue" export default { // 第二步:注入组件 components:{ Header, Main, Aside } } </script> <template> <Header /> <Main /> <Aside /> </template>
3 注册方式
全局注册虽然很方便,但有以下几个问题:
<script> import Item from "./Item.vue" export default { components : { Item } } </script> import { createApp } from 'vue' import App from './App.vue' import Header from "./components/Header.vue" const app = createApp (App )app.component ("Header" ,Header ) app.mount ('#app' )
4 组件传递数据
组件与组件之间不是完全独立的,而是有交集的,那就是组件与组件之间是可以传递数据的传递数据的解决方案就是props
。
通过props
传递数据,不仅可以传递字符串类型的数据,还可以是其他类型,例如:数字、对象、数组等。但实际上任何类型的值都可以作为props
的值被传递。
props
传递数据,只能从父级传递到子级,不能反其道而行。
<!-- 父组件 --> <template> <h3>Parent</h3> <!-- 传递数据 --> <Child title="Parent数据" demo="测试" /> <!-- 传递动态数据 --> <Child :msg="message" :age="age" /> <!-- 传递数组数据 --> <Child :names="names" /> <!-- 传递对象数据 --> <!-- <Child :userInfo="userInfo" /> --> </template> <script> import Child from "./Child.vue" export default { data(){ return{ message:"Parent dynamic data!", age:20, names:["yui","mio","tsumugi"], userInfo:{ name: "kurumi", age: 16 } } }, components:{ Child } } </script> <!-- 子组件 --> <!-- 没有的参数默认是传递undefined --> <template> <h3>Child</h3> <p>{{ title }}</p> <p>{{ demo }}</P> <p>{{ msg }}</P> <p>{{ age }}</P> <ul> <li v-for="(item, index) of names" :key="index">{{ item }}</li> </ul> <p>{{ userInfo.name }}</p> <p>{{ userInfo.age }}</p> </template> <script> export default { data(){ return{} }, // 子组件接收数据 props: ["title","demo","msg","age","names","userInfo"] } </script>
5 组件数据验证
添加组件数据验证:
type:类型验证
default:默认值
required:是否必须
validator:自定义验证函数
主:prop
的数据是只读的,不可修改。
<!-- 父组件 --> <template> <h3>Parent</h3> <Child :single="single" :multiple="multiple"/> </template> <script> import Child from "./Child.vue" export default { data(){ return{ single: 20, multiple: "123" } }, components: { Child } } </script> <!-- 子组件 --> <template> <h3>Child</h3> <p>{{ single }}</P> <p>{{ multiple }}</P> <p>{{ age }}</P> <p v-for="(item,index) of names" :key="index">{{ item }}</p> </template> <script> export default { data() { return {} }, props: { // 数据验证 single: { // 类型验证,验证不通过爆警告 type: String, // 必须项 required: true }, multiple: { // 多个类型验证 type: [Number, Array, Object] }, age: { type: Number, // 默认值 default: 10 }, names: { type: Array, // 数字可以字符串可以直接default,但是数组或对象必须用工厂函数返回默认值 default(){ return ["yui","mio","tsumugi"] } } } } </script>
<!-- 验证类的同时还验证类中的数据 --> <script> export default { props: { value: { type: Object, required: true, validator: (value) => { return typeof value.title === 'string' && typeof value.content === 'string' && typeof value.src === 'string' && typeof value.time === 'number'; }, }, }, } </script>
6 组件事件
在组件的模板表达式中,可以直接使用$emit
方法触发自定义事件。
触发自定义事件的目的是组件之间传递数据。
组件之间数据传递:
<!-- 父组件 --> <template> <h3>组件事件</h3> <Child @someEvent="getHandle" /> <p>父元素:{{ message }}</p> </template> <script> import Child from "./Child.vue" export default { data(){ return{ message: "" } }, components:{ Child }, methods:{ getHandle(data){ this.message = data; } } } </script> <!-- 子组件 --> <template> <h3>Child</h3> <button @click="clickEventHandle">传递数据</button> </template> <script> export default { data(){ return{ msg: "Child数据!" } }, methods: { clickEventHandle(){ //自定义事件 this.$emit("someEvent",this.msg) } } } </script>
7 组件侦听
组件配合v-model
使用,可以实现一个组件读取数据,另一个组件接收数据。
<!-- 父组件 --> <template> <h3>Main</h3> <p>搜索内容为:{{ search }}</p> <Child @searchEvent="getSearch" /> </template> <script> import Child from "./Child.vue" export default { data(){ return{ search: "" } }, components: { Child }, methods: { getSearch(data){ this.search = data; } } } </script> <!-- 子组件 --> <template> 搜索:<input type="text" v-model="search"> </template> <script> export default { data(){ return{ search: "" } }, //侦听器 watch: { search(newValue,oldValue){ this.$emit("searchEvent", newValue) } } } </script>
8 props实现子传父
props
可以传递数据,因此可以利用子组件传递函数,在函数中携带参数,从而利用props实现子传父。
<!-- 父组件 --> <template> <h3>ComponentA</h3> <p>父元素:{{ message }}</p> <Child title="标题" :onEvent="dataFn"/> </template> <script> import Child from "./Child.vue" export default { data(){ return{ message: "" } }, components: { Child }, methods: { dataFn(data){ this.message = data; } } } </script> <!-- 子组件 --> <template> <h3>Child</h3> <p>{{ title }}</p> <!-- 类似回调函数 --> <p>{{ onEvent('传递数据')}}</p> </template> <script> export default { data(){ return{} }, props: { title: String, onEvent: Function } } </script>
9 依赖注入
正常情况下,如果祖父的数据要传递给孙子,需要先传递给父亲,再传递给孙子,这一问题成为”prop 逐级透传”。
provide
和inject
可以帮助我们解决这一问题。一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
provide
和inject
只能由上到下传递,不能反向传递。
<!-- grandparent.vue --> <template> <h3>Grandparent</h3> <Parent/> </template> <script> import Parent from "./Parent.vue" export default { data(){ return{ message: "Grandparent Data" } }, // 通过Data赋值 provide(){ return{ message: this.message } }, // 直接赋值 // provide:{ // message: "爷爷的财产" // }, components:{ A } } </script> <!-- parent.vue --> <template> <h3>Parent</h3> <Child /> </template> <script> import Child from "./child.vue" export default { components: { Child }, } </script> <!-- child.vue --> <template> <h3>Child</h3> <p>{{ message }}</p> </template> <script> export default { inject: ["message"] } </script>
import { createApp } from 'vue' import App from './App.vue' import Header from "./components/Header.vue" const app = createApp (App )app.provide (name,data) app.provide ("message" ,"I'm a message" ) app.mount ( '#app' )
10 插槽
用于在组件之间传递模板。
<slot>
元素是一个插槽出口(slot outlet),标示了父元素提供的插槽内容(slot content)将在哪里被渲染。
使用时,父元素模板定义子元素,在子元素中写模板。子元素使用<slot>
双标签调用出父元素的定义的内容。
插槽中可以书写动态数据,动态数据是存储在父组件中的。
插槽可以有默认值,当父组件的插槽中内容为空时,将显示子组件中插槽标签中的内容。
具名插槽:给插槽一个属性值,用于区分不同的插槽。写法是<template v-slot:name>
,可以简写成<template #name>
<!-- 父组件 --> <template> <!-- 父元素中的子元素标签用双标签,在标签中写模板 --> <Child> <div> <h3>slot title</h3> <p>slot content</p> <p>{{ content }}</p> </div> </Child> <!-- 通过名字区分插槽 --> <Child> <template v-slot:first> <h3> I'm first </h3> </template> <template #second> <h3> I'm second</h3> </template> </Child> <!-- 使用父子组件的数据,:name="别名",然后点出里面的属性 --> <Child v-slot:second="childData"> <h3>{{ message }} -- {{ childData.msg }}</h3> </Child> </template> <script> import Child from "./Child.vue" export default { data(){ return{ content: "Hello World!", message: "Parent Data!" } }, components: { Child }, } </script> <!-- 子组件 --> <template> <h3>Child</h3> <!-- 使用插槽。注:标签名不能改--> <!-- 将父组件中插槽中的内容注释后,将显示下列默认内容 --> <slot>无name默认值</slot> <!-- 用name指向父组件中v-slot指向的模板 --> <slot name="first">first默认值</slot> <!-- 传递msg数据给父组件 --> <slot name="second" :msg="childMessage">second默认值</slot> </template> <script> export default{ data(){ return{ childMessage:"Child Data!" } } } </script>
11 组件生命周期
生命周期函数
创建期:beforeCreate、created
挂载期:beforeMount、mounted
更新期:beforeUpdate、updated
销毁期:beforeUnmount、unmounted
当组件进入对应的时期后,将执行对应的函数的内容。
<template> <h3>组件生命周期</h3> <p>{{ message }}</p> <button @click="updateHandle">更新数据</button> </template> <script> export default { data(){ return{ message: "老数据" } }, methods: { updateHandle(){ this.message="新数据" } }, beforeCreate(){ console.log("组件创建之前"); }, created(){ console.log("组件创建之后"); }, beforeMount(){ console.log("组件渲染之前"); }, mounted(){ console.log("组件创建之后"); }, beforeupdate(){ console.log("数据更新之前"); }, updated(){ console.log("数据更新之后"); }, beforeUnmount(){ console.log("组件卸载之前"); }, unmounted(){ console.log("组件卸载之后"); } } </script>
12 生命周期应用
常见应用:
通过ref
获取元素DOM结构。在beforeMount()
前无法获得,因为组件未挂载。在mounted()
后可以操作DOM结构,此时组件挂载。
模拟网络请求渲染数据。在beforeCreate()
前无法获得,因为组件未创建,没有data()
,数据无法存储。在created()
后则可以将获得的数据存储在data()
中。但是实际上为了让网页先显示结构再显示数据,所以一般是在mounted()
中作数据获得的操作,因为先获得数据没有结构也没有实际作用。
<!-- 读取DOM结构 --> <template> <h3>组件生命周期函数应用</h3> <p ref="name">百战程序员</p> </template> <script> export default { beforeMount(){ // 此时组件未挂载,获得undefined console.log(this.$refs.name); }, mounted(){ // 此时组件挂载,获得百战程序员 console.log(this.$refs.name); } } </script> <!-- 模拟网络请求渲染数据 --> <!-- 结果只有mio,因为在创建之前没有banner,banner还是undefined --> <template> <ul> <li v-for="(item,index) of banner" :key="index"> <h3>{{ item.title }}</h3> <p>{{ item.content }}</p> </li> </ul> </template> <script> export default { data(){ return{ banner: [] } }, beforeCreate(){ // 模拟网络获得数据 this.banner = [ { title: "uyi", content: "guitar" } ] }, created(){ // 模拟网络获得数据 this.banner.push( { title: "mio", content: "bass" } ) }, // 为了让网页先显示结构再显示数据,所以一般是在mounted中作数据获得的操作 mounted(){ this.banner.push( { title: "tsumugi", content: "keyboard" } ) } } </script>
13 组件切换
有些场景会需要在两个或多个场景之间来回切换,比如Tab界面。
当使用<component is="...">
来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过<keep-alive>
组件强制被切换掉的组件仍然保持“存活”的状态。
在下列案例中,如果不加上<keep-alive>
,切换组件时,组件会被卸载,这样更新了数据后再切换数据也会重置。但是加上<keep-alive>
后组件将不会卸载,数据也就能保存下来。
<!-- Parent.vue --> <template> <!-- keep-alive必须包裹确定的组件,不能再包裹button,否则报错 --> <keep-alive> <!-- 加载组件,is绑定 --> <component :is="tabComponent"></component> </keep-alive> <button @click="changeHandle">切换组件</button> </template> <script> import A from "./A.vue" import B from "./B.vue" export default { data(){ return{ // 组件名绑定,要用字符串 tabComponent : "A", } }, components:{A,B}, methods:{ changeHandle(){ // 切换组件,要用字符串 this.tabComponent = this.tabComponent == "A" ? "B" : "A" } }, } </script> <!-- A.vue --> <template> <h3>我是A</h3> <p>{{ message }}</p> <button @click="updateHandle">更新数据</button> </template> <script> export default { data(){ return{ message: "老数据" } }, beforeUnmount(){ console.log("组件卸载之前"); }, unmounted(){ console.log("组件卸载之后"); }, methods: { updateHandle(){ this.message = "新数据" } } } </script> <!-- B.vue --> <template> <p>我是B</p> </template> <script> export default { data(){ return{ } }, beforeUnmount(){ console.log("组件卸载之前"); }, unmounted(){ console.log("组件卸载之后"); } } </script>
14 异步组件
在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue提供了defineAsyncComponent
方法来实现此功能。
下列案例中,若不做成异步,则在网络请求中会一次性加载A,B。若将B做成了异步请求,则一开始并不会请求B,当用户点击切换后才会在网络中请求B。请求后将保存在本地,多次切换后都不会再发送网络请求。
<!-- Parent.vue。A.vue和B.vue在上一节 --> <template> <component :is="tabComponent"></component> <button @click="changeHandle">切换组件</button> </template> <script> // 导入组件 import { defineAsyncComponent } from 'vue' import A from "./A.vue" // 设置成异步 const B = defineAsyncComponent(() => import('./B.vue')) export default { data(){ return{ // 组件名绑定,要用字符串 tabComponent : "A", } }, components:{A,B}, methods:{ changeHandle(){ // 切换组件,要用字符串 this.tabComponent = this.tabComponent == "A" ? "B" : "A" } }, } </script>
四、Vue其他 1 透传 Attributes
“透传attribute”
指的是传递给一个组件,却没有被该组件声明为props
或emits
的attribute
或者v-on
事件监听器。最常见的例子就是class
、style
和id
。当一个组件以单个元素为根作渲染时,透传的attribute
会自动被添加到根元素上。
注:在实际场景中并不常用。
<!-- 父组件 --> <template> <Child class="attr-container" /> </template> <script> import Child from "./Child.vue" export default { components: { Child } } </script> <!-- 子组件 --> <template> <!-- 必须是唯一根元素 --> <h3>透传属性</h3> <!-- <p>再多加一个标签就会失效</p> --> </template> <script> // 禁用属性继承,添加后class将不会继承下来 // export default{ // inheritAttrs: false // } </script> <style> .attr-container{ color: red; } </style>
2 引入第三方库 2.1 Swiper
常用动画库:offifcial::Swiper Vue.js
首先:npm i swiper
进行安装
<!-- 导航+轮播图案例 --> <template> <!-- 导航栏属性设置 --> <swiper :navigation="true" :modules="modules" class="mySwiper"> <!-- 单个栏目设置 --> <swiper-slide > <div class="slide slide1"></div> </swiper-slide> <swiper-slide > <div class="slide slide2"></div> </swiper-slide> <swiper-slide > <div class="slide slide3"></div> </swiper-slide> <swiper-slide > <div class="slide slide4"></div> </swiper-slide> <swiper-slide > <div class="slide slide5"></div> </swiper-slide> </swiper> </template> <script> // 导入轮播图 import { Swiper, SwiperSlide } from 'swiper/vue'; // 导入导航栏 import { Navigation } from 'swiper/modules'; // 导入对应css import 'swiper/css'; import 'swiper/css/navigation'; export default { // 导航栏注册 setup() { return { modules: [Navigation], } }, // 轮播图注册 components: { Swiper, SwiperSlide, }, }; </script> <style scoped> .swiper-slide { height: 405px; display: flex; align-items: center; justify-content: center; } /* 切割图片 */ .slide{ height: 405px; width: 144px; } .slide1 { background: url("src/assets/img/mygo.png") no-repeat 0px 0px; } .slide2 { background: url("src/assets/img/mygo.png") no-repeat -144px 0px; } .slide3 { background: url("src/assets/img/mygo.png") no-repeat -288px 0px; } .slide4 { background: url("src/assets/img/mygo.png") no-repeat -432px 0px; } .slide5 { background: url("src/assets/img/mygo.png") no-repeat -576px 0px; } </style>
3 Vuex
Vuex是一个专为Vue.js
应用程序开发的状态管理模式+库 。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
简单来说,状态管理可以理解成为了更方便的管理组件之间的数据交互,提供了一个集中式的管理方案,任何组件都可以按照指定的方式进行读取和改变数据。
最常用的核心概念包含:
state:存储数据
Getter:对数据进行过滤
Mutation:修改数据
Action:类似Mutation,但是Action是提交Mutation,不是变更状态,并且Action可以包含任意异步操作
official::Vuex (vuejs.org)
安装:npm i vuex
新建文件:src/store/index.js
import { createStore } from "vuex" export default store = createStore ({ state : { counter : 0 ; }, getters : { getCount (state ){ return state.counter > 0 ? state.counter : "counter小于0,不符合要求" } }, mutatons : { addCounter (state, num ){ state.counter += num; } }, actions : { asyncAddCounter ({ commit } ){ axios.get ("http://iwenwiki.com/api/generator/list.php" ) .then (res => { commit ("addCounter" , res.data [0 ]) }) } } })
import store from "./store" createApp (App ).use (store).mount ("#app" );
<!-- 直接在组件中进行使用 --> <!-- 读取方法1:用于单次 --> <template> <p>counter = {{ $store.state.counter }}</p> <p>counter = {{ $store.state.getCount }}</p> <button @click="addClickHandle">增加</button> <button @click="addAsyncClickHandle">异步增加</button> </template> <script> export default{ methods: { addClickHandle(){ // 固定调用方式 this.$store.commit("addCounter", 10); }, addAsyncClickHandle(){ this.$store.dispatch("asyncAddCounter"); } } } </script> <!-- 读取方法2:用于多次 --> <template> <p>counter = {{ counter }}</p> <p>counter = {{ getCounter }}</p> <button @click="addClickHandle">增加</button> </template> <script> import { mapState, mapGetters, mapMutations } from "vuex" export default{ computed: { // 专门来读取vuex的数据 ...mapState(["counter"]), ...mapGetters(["getCounter"]) }, methods: { ...mapMutations(["addCounter"]), addClickHandle(){ this.addCounter(20); }, addAsyncClickHandle(){ this.asyncAddCounter(); } } } </script>
4 Pinia
由于 Pinia 在生态系统中能够承担相同的职责且能做得更好,因此 Vuex 现在处于维护模式。对于新的应用,建议使用 Pinia。
official::Pinia (vuejs.org)
安装:npm i pinia
新建:src/stores/index.js
使用:
State:属性
Method:方法
Getter:基于 store 中 state 创建的计算属性,它返回的是派生自 state 的值,并且会根据 state 的变化自动更新
Action:用于修改 state 或执行副作用(如异步操作)的方法。在 Pinia 中,可以直接在 action 内部修改 state,而无需通过 mutation。
import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const app = createApp (App );app.use (createPinia ()); app.mount ('#app' );
import { defineStore } from 'pinia' import { ref } from 'vue' export const useCsrfTokenStore = defineStore ('csrf' ,() => { const csrfToken = ref ('' ); const get = ( ) => { return csrfToken.value ; }; const set = (token ) => { csrfToken.value = token;}; const tokenIsNotEmpty = computed (() => !!csrfToken.value ); async fetchAndSetToken ( ) { const response = await axios.get ('/api/csrf-token' ); set (response.data .token ); } return { get, set, tokenIsNotEmpty, fetchAndSetToken }; })
<!-- 使用 --> <template> <div> <button @click="fetchAndSetToken">Fetch and Set Token</button> <p v-if="tokenIsEmpty">No token fetched yet.</p> <p v-else>Current token: {{ csrfToken }}</p> </div> </template> <script setup> import { useCsrfTokenStore } from '@/stores/csrf'; const csrfTokenStore = useCsrfTokenStore(); // 直接使用 store 的 state 和 methods const { csrfToken, tokenIsEmpty, fetchAndSetToken } = csrfTokenStore; </script>
五、Vue网络 1 Vue结构
应用实例:每个Vue应用都是通过createApp
函数创建一个新的应用实例。
根组件:我们传入createApp
的对象实际上是一个组件,每个应用都需要一个”根组件”,其他组件将作为其子组件。
挂载应用:应用实例必须在调用了.mount()
方法后才会渲染出来。该方法接收一个”容器”参数,可以是一个实际的DOM元素或是一个CSS选择器字符串。
src/assets
:存放css,图片等资源。
<!-- App .vue --> import { createApp } from 'vue' import App from './App.vue' const app = createApp (App )mount ('#app' )
<body > <div id ="app" > </div > <script type ="module" src ="/src/main.js" > </script > </body >
2 main.js
vue代码最后都会被编译成main.js,然后引入main.js执行代码。
import { createApp } from 'vue' import App from './App.vue' const app = createApp (App );app.config .globalProperties .$serverIP = 'localhost:8000' ; app.mount ('#app' )
<script> // any.vue export default { created() { // 使用全局变量 console.log(this.$serverIP); } }; </script>
3 axios
基于promise网络请求库,作用于node.js和浏览器中。
安装:npm i axios
文档:official::Axios中文文档
4 路由配置
安装:
路径创建:
/src/api/index.js
:访问网页
/src/api/path.js
:网络路径
/src/config/conster.js
:本地网址管理
/src/utils/request.js
:网络请求处理
import axios from "../utils/request.js" import path from "./path.js" const api = { getIndex ( ){ return axios.get (path.baseUrl + path.suffix ); } } export default api;
const base = { baseUrl : "localhost:8000" , suffix : "/iAmIn.html" } export default base;
let module = { dev : 'http://127.0.0.1:5173' , test : '' , pro : '' } export const BASE_URL = module .dev
import axios from "axios" import qs from 'qs' ;import { BASE_URL } from '/src/config/conster.js' const instance = axios.create ({ baseURL : BASE_URL , timeout : 5000 }) instance.interceptors .request .use ( config => { if (config.method === "post" ){ config.data = qs.stringify (config.data ); } return config; }, error => { return Promise .reject (error); } ) const errorHandle = (status,info ) =>{ switch (status){ case 400 : console .log ("语义有误" );break ; case 401 : console .log ("服务器认证失败" );break ; case 403 : console .log ("服务器拒绝访问" );break ; case 404 : console .log ("地址错误" );break ; case 500 : console .log ("服务器遇到意外" );break ; case 502 : console .log ("服务器无响应" );break ; default : console .log (info);break ; } } instance.interceptors .response .use ( response => { NProgress .done (); return response.status === 200 ? Promise .resolve (response) : Promise .reject (response); }, error => { NProgress .done (); const { response } = error; errorHandle (response.status , response.info ); } ) export default instance;
<!-- 使用api --> <script> import api from "../api/index.js" export default{ mounted(){ api.getIndex().then(res =>{ console.log(res.data); }) } } </script>
5 跨域问题
JS采取的是同源策略。
同源策略是浏览器的一项安全策略,浏览器只允许js代码请求和当前所在服务器域名,端口,协议相同的数据接口上的数据,这就是同源策略。
也就是说,当协议、域名、端口 任意一个不相同时,都会产生跨域问题,所以又应该如何解决跨域问题呢。
跨域常见异常:
Access to XMLHttpRequest at "url" from origin "url" has been blocked by CORS policy: No 'Access-Control-Allow-Origin’ header is present on the requested resource.
GET
net::ERR_FAILED 200
Uncaught (in promise)
后台跨域:cors
前台跨域:proxy
解决完跨域配置之后,要重启服务器才有用。
注:配置代理的时候'/api'
如果直接写成'/'
,这样表示http://127.0.0.1:80/
后面的路由都进行代理,这样会导致你加载本地资源会出错,因为你把加载本地资源的路径全部都代理到服务端去了,浏览器会向服务器进行资源请求,这样就会导致页面报错。
切记 :本地同时前后端联合调试的时候,目标请求地址不能用 localhost,必须改成 127.0.0.1,否则会报错:http proxy error: Error: connect ECONNREFUSED ::1:8000
。
export default defineConfig ({ plugins : [vue ()], server : { https : true , proxy : { '/api' : { target : '<url>' , changeOrigin : true , ws : true , rewrite : (path ) => path.replace (/^\/api/ , '' ) } } }, })
6 页面跳转
在Vue中,我们可以通过vue-router
路由管理页面之间的关系。
Vue Router是Vue.js的官方路由。它与Vue.js核心深度集成,让用Vue.js构建单页应用变得轻而易举。
安装:npm i vue-router
路径创建:
/src/router/index.js
:路由配置文件
/src/views/name.vue
:路由对应的文件
histroy的参数:
createwebHashHistory()
:显示出的网址如下:home:http://localhost : 8080/#/
,about:http://localhost:8080/# / about
。原理是a标签锚点连接。
createwebHistory()
:显示出的网址如下:home:http://localhost:8080
,about:http://localhost:8080/ about
。此种方式,需要后台配合做重定向,否则会出现404问题。原理是H5的pushState()
createWebHashHistory使用URL的哈希部分(如example.com/#/path)作为路径,这种模式下,浏览器不会发送实际的请求,而是通过监听URL的变化来实现前端路由的跳转。这种模式的优点是兼容性好,甚至可以在不支持HTML5历史模式的浏览器中正常使用,但URL中的哈希部分可能不太美观。
createWebHistory使用真实的URL路径(如example.com/path),需要服务器的支持,通过HTML5的history API来实现前端路由的跳转。这种模式的优点是URL更加美观,没有哈希部分,但兼容性可能稍差,需要服务器的配置支持。
需要注意的是,createWebHistory只有在服务器配置正确的情况下才能正常工作,而createWebHashHistory则不需要服务器配置。因此,在选择使用哪种创建函数时,需要考虑项目的需求以及服务器的配置情况。
import { createRouter, createWebHashHistory } from 'vue-router' import HomeView from '../views/index.vue' const routes = [ { path : '/' , name : 'home' , component : HomeView }, { path : '/about' , name : 'about' , component : () => import ('../views/about.vue' ) } ] const router = createRouter ({ history : createWebHashHistory (), routes }) export default router;
<!-- App.vue --> <template> <!-- 设置路由跳转 --> <router-link to="/">首页</router-link> <router-link to="/about">关于</router-link> <!-- 显示路由 --> <router-view></router-view> </template>
import { createApp } from 'vue' import App from './App.vue' import router from './router' createApp (App ).use (router).mount ('#app' )
7 路由带参
在路由中携带参数。常见业务是页面中存在列表项,然后点击每个列表项查看详情。
{ path : "/list/:name[/:other...]" , name : "list" , component : () => import ("../views/ListView.vue" ) }
<!-- vue中带参 --> <li><router-link to="/list/内蒙">内蒙旅游十大景区</router-link></1i> <li><router-link to="/list/北京">北京旅游十大景区</router-link></li> <li><router-link to="/list/四川">四川旅游十大景区</router-link></li>
<!-- 详细界面中使用参数 --> <p> {{ $route.params.name}} 城市旅游景区详情</p>
8 路由嵌套
页面中继续有页面选项。
const routes = [ { path : '/about' , name : 'about' , redirect : "/about/us" , component : () => import ("../views/AboutView.vue" ), children :[ { path : "us" , component : () => import ("../views/AboutSub/AboutUS.vue" ) }, { path : "info" , component : () => import ("../views/AboutSub/AboutInfo.vu e" ) } ] } ]
<!-- vue中使用 --> <template> <div class="about"> <!-- 直接写路径即可 --> <router-link to="/about/us">关于我们</router-link> <router-link to="/about/info">关于信息</router-link> <router-view></router-view> </template>
六、Vue3 1 升级
六大亮点:
Performance:性能更比Vue 2.0强。
Tree shaking support:可以将无用模块”剪辑“,仅打包需要的。
composition API:组合API
Fragment, Teleport, Suspense:”碎片“,Teleport即Protal传送门,”悬念“
Better TypeScript support:更优秀的Ts支持(Vue3的底层是Ts)
Custom Renderer APl:暴露了自定义渲染API
2 组合式API
Vue3组合了API,在setup()
中,可以定义数据、方法、props和context等。
在2.x中,组件的方法中可以通过this获取到当前组件的实例,并执行data变量的修改,方法的调用,组件的通信等等,但是在3.x中,setup()在beforeCreate和created时机就已调用,无法使用和2.x一样的this。但是可以通过接收setup(props,ctx)
的方法,获取到当前组件的实例和props。总之,在setup中,this指向的是undefined
<template> <div class="hello"> <p>{{ message }}</p> <ul> <li v-for="(item,index) in names.list" :key="index">{{ item }}</li> </ul> <button @click="clickHandle">按钮</button> <p>{{ msg }}</p> </div> </template> <script> import { ref, reactive } from "vue" export default { name:'Helloworld', // 定义props接收数据 props:{ msg:String }, //组合式API(props传参,ctx表示this) setup(props,ctx) { // ref:定义简单数据 const message = ref("我是消息") //reactive:定义复杂数据(数组、对象) const names = reactive({ list: [ "yui", "mio" , "tsumugi"], }) // 定义函数 function clickHandle(){ message.value="我是新的消息"; } // props赋值 const msg = props.msg; // 查看ctx的数据(包括attrs、emit、slots等) console.log(ctx); // 定义的数据需要返回出去才能被使用 return{ message, names, // 函数也需要返回 clickHandle, msg } } } </script>
<!-- 直接将setup写到标签中,不用return --> <template> <div class="hello"> <p>{{ message }}</p> <ul> <li v-for="(item, index) in names.list" :key="index">{{ item }}</li> </ul> <button @click="clickHandle">按钮</button> <p>{{ msg }}</p> </div> </template> <script setup> import { ref, reactive } from "vue" // 获取props数据 const props = defineProps({ msg: String }) // ref:定义简单数据 const message = ref("我是消息") // reactive:定义复杂数据(数组、对象) const names = reactive({ list: ["yui", "mio", "tsumugi"], }) // 定义函数 function clickHandle() { message.value = "我是新的消息" } // props赋值 const msg = props.msg </script>
3 setup使用生命周期函数
Options API
Hook inside setup
beforeCreate
Not needed*
created
Not needed*
beforeMount
onBeforeMount
mounted
onMounted
beforeUpdate
onBeforeUpdate
updated
onUpdated
beforeUnmount
onBeforeUnmount
unmounted
onUnmounted
<template> <div class="hello"></div> </template> <script setup> import { onMounted } from "vue"; // 比以前有优势,以前同一个生命周期函数只能存在一个,现在可以存在多个 onMounted(() =>{ console.log("生命周期函数:onMounted1");}); onMounted(() =>{ console.log("生命周期函数:onMounted2");}); </script>
4 依赖注入
Provide / lnject
:
provide()
和inject()
可以实现嵌套组件之间的数据传递。
这两个函数只能在setup()
函数中使用。
父级组件中使用provide()
函数向下传递数据。
子级组件中使用inject()
获取上层传递过来的数据。
不限层级。
<!-- 父组件的script --> <script setup> import { provide } from "vue" proivde("customVal", "我是父组件向子组件传递的值"); </script> <!-- 子组件的script --> <script setup> import { inject } from "vue" const customVal = inject("customVal"); </script>
七、element-plus 1 基础使用
css组件库
official::Element Plus (element-plus.org)
必须安装:npm i element-plus
按需引入额外安装:
npm i unplugin-vue-components
npm i unplugin-auto-import
import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp (App )app.use (ElementPlus ) app.mount ( ' #app ' )
import { defineConfig } from 'vite' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' export default defineConfig ({ plugins : [ AutoImport ({ resolvers : [ElementPlusResolver ()], }), Components ({ resolvers : [ElementPlusResolver ()], }), ], })
<template> <el-row class="mb-4"> <el-button>Default</el-button> <el-button type="primary">Primary</el-button> <el-button type="success">Success</el-button> <el-button type="info">Info</el-button> <el-button type="warning">Warning</el-button> <el-button type="danger">Danger</el-button> </el-row> </template>
2 字体处理
字体使用需要额外安装。
安装:npm i @element-plus/icons-vue
创建文件:src/plugins/icons.js
import * as components from "@element-plus/icons-vue" ;export default { install : (app ) => { for (const key in components) { const componentconfig = components[key]; app.component (componentconfig.name ,componentconfig); } }, };
import elementIcon from "./plugins/icons" app.use (elementIcon)
<!-- 使用图标 --> <el-icon class="expand" color="#409EFC" :size="30"> <expand /> </el-icon>
八、其他插件 1 顶端加载条
在顶端新增一个路由加载条,可以看见网页的加载进度。
安装:npm i nprogress -S
import NProgress from 'nprogress' import 'nprogress/nprogress.css' router.beforeEach ((to,from ,next))=>{ NProgress .start (); next (); } router.afterEach ((to,from ))=>{ NProgress .done (); }
九、其他问题 1 网络
Q1:GET http://127.0.0.1:5173/api/mainPage/login.html net::ERR_CONNECTION_REFUSED
A1:只能用localhost访问,否则修改package.json——scripts——dev——vite --host IP
来新增IP。
2 访问类中元素
Q1:this.id
无法访问到id
值。
A1:
const menuItems = ref ([ { id : "1" , name : "首页" , router : this .id , subMenu : null , }, { id : "2" , name : "消息" , router : this .id , subMenu : null , ]);
3 无限发包
什么getter
,undefined
const value = computed (props.value )const value = computed (() => props.value )